/*!  \file  Stack.cpp
 *	  \brief  
 * 
 *   \details   details
 *   \author    Yordan Madzhunkov German Research School for Simulation Sciences GmbH
 *   \version   1.0
 *   \date      07/16/2013 10:32:13 AM
 *   \copyright GNU Public License.
 */
#include <pthread.h>
#include <iostream>
#include "macros.h"
#include "Lock.h"
#include "Task.h"
#include "Stack.h"
#include "UnitTester.h"
template <typename T>
Stack<T>::Stack(const int maxSize_) {
	maxSize = maxSize_;
	storage = new T [maxSize   ];
	count = 0;
}
template <typename T>
Stack<T>::~Stack() {
	if (storage) delete[] storage; storage = 0;
}

	
template <typename T>
bool Stack<T>::push   (const T   id) {
	countLock.lock(); 
	bool res = count < maxSize;
	if (res) { storage[count++] = id; }
	countLock.unlock();
	return res;
}

template <typename T>
bool Stack<T>::tryPush(const T   id) {
	if (countLock.tryLock()) {
		bool res = count < maxSize;
		if (res) { storage[count++] = id; }
		countLock.unlock();
		return res;
	} else {
		return false;
	}
}

template <typename T>
bool Stack<T>::pop    (      T & id) {
	countLock.lock(); 
	bool res = count > 0;
	if (res) { id = storage[--count]; }
	countLock.unlock();
	return res;

}
template <typename T>
bool Stack<T>::tryPop (      T & id) {
	if (countLock.tryLock()) {
		bool res = count > 0;
		if (res) { id = storage[--count]; }
		countLock.unlock();
		return res;
	} else 
		return false;
}

template <typename T>
void Stack<T>::reset() {
	countLock.lock(); 
	count = 0;
	countLock.unlock();
}

// Explicit template instantiation
template class Stack<int>;
template class Stack<void*>;

class TestStack : public UnitTest {
public:
	TestStack ();
	virtual ~TestStack (){};
	virtual bool test() const;
	// given two sorted arrays counts how many common elements they have
	int count(int * RS first , const int nFirst, 
				 int * RS second, const int nSecond) const;

};

using namespace std;
TestStack::TestStack() {
	setName 				("Test Stack");
	setDescription  	("Test Stack for correct bahaviour & memory allocation");
	setTestAuthor		("Yordan Madzhunkov");
	setDeveloperAuthor("Yordan Madzhunkov");
	setCompileTime    (__DATE__, __TIME__); // each class that inherits this one should also have this line.
};
struct Params {
	Stack<int> * q;
	int * input;
	int * output;
	int id, nIn, nOut, nInFail, nOutFail;
	int nSamples;
};
#include "RandomGenerator.h"
void * start_routine2(void * arg) {
	Params * p = reinterpret_cast<Params *> (arg);
//	char buf[128];	sprintf(buf, "Hello I am thread %d\n", p->id); cout << buf;
	Stack<int> & q = *p->q;
	RandomGenerator * rng = RandomGenerator::defaultGenerator(p->id * 100 + 34);
	p->input  = new int[p->nSamples];
	p->output = new int[p->nSamples];
	p->nOut   = 0; 
	p->nIn    = 0;
	p->nInFail   = 0;
	p->nOutFail  = 0; // Stack is empty 

	for (int i = 0; i < p->nSamples; i++) {
		int id = i + p->id * p->nSamples;	
		if (rng->getUniform() < 0.50) {
			if (q.push(id))
				p->input[p->nIn++] = id;
			else p->nInFail++;
		} else {
			if (q.pop(id))
				p->output[p->nOut++] = id;
			else p->nOutFail++;
		}
	}
	int id = 0; 
	// pop until queue is empty or we run out of space
	while (p->nOut < p->nSamples && q.pop(id) ) {
		p->output[p->nOut++] = id;
	}
	if (rng) delete rng; rng = 0;
	sort( p->input, p->input  + p->nIn);
	sort(p->output, p->output + p->nOut);
	return NULL;
}
bool TestStack::test() const {
	int nThreads = 4;
	Stack<int> q(16 * 16);
	bool res = true;
	for (int i = 0; i < 10; i++) {
		q.push(i);
	}
	for (int i = 9; i >= 0; i--) {
		int r = 0;	q.pop(r);
		res &= i == r;
	}
	assert(res);
	pthread_t * threadIDs = new pthread_t[nThreads];
	Params    * params    = new Params   [nThreads];
	q.reset();
	for (int i = 0; i < nThreads; i++) {
		params[i].q = &q;
		params[i].id = i;
		params[i].input  = NULL;
		params[i].output = NULL;
		params[i].nSamples =  256 * 256;
	}
	for (int i = 1; i < nThreads; i++) {
		void * arg = reinterpret_cast<void *> (&params[i]);	
		int info = pthread_create(&threadIDs[i], NULL, &start_routine2, arg);
		assert(info == 0);
	}

	// use the current thread as well
	start_routine2(&params[0]);

	// join all theads	
	for (int i = 1; i < nThreads; i++) if (threadIDs[i]) {
		void * exitPtr = NULL;
		pthread_join(threadIDs[i], &exitPtr);	
		threadIDs[i] = 0;		
	}
	int c[nThreads][nThreads];
	int sumRows[nThreads]; memset(sumRows, 0, sizeof(sumRows));
	int sumCols[nThreads]; memset(sumCols, 0, sizeof(sumCols));
	for (int i = 0; i < nThreads; i++) {
		for (int j = 0; j < nThreads; j++) {
			c[i][j] = count(  params[i].input ,  params[i].nIn,
									params[j].output,  params[j].nOut);
			sumRows[i] += c[i][j];
			sumCols[j] += c[i][j];
		}
	}
	for (int i = 0; i < nThreads; i++) {
		res &=  sumRows[i] == params[i].nIn;
		res &=  sumCols[i] == params[i].nOut;
		if (params[i].output) delete [] params[i].output; 	params[i].output = 0;
		if (params[i].input ) delete [] params[i].input ; 	params[i].input  = 0;
	}
	if (threadIDs) delete [] threadIDs; threadIDs = 0;
	if (   params) delete []    params;    params = 0;
	return res;
}
int TestStack::count(int * RS first , const int nFirst, 
				 int * RS second, const int nSecond) const {
	int f = 0, s = 0, res = 0;
	while (f < nFirst && s < nSecond) {
		if (first[f] == second[s]) {
			res++; f++; s++;
		} else if (first[f] < second[s]) f++;	else 	s++;
	}
	return res;
}
TestStack testStack;
